
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <iomanip>
#include <log.h>
#include <algorithm>

using namespace std;

namespace ioscolor
{
    // the following are UBUNTU/LINUX ONLY terminal color codes.
    const string RESET = "\033[0m";               /* defult */
    const string BLACK = "\033[30m";              /* Black */
    const string RED = "\033[31m";                /* Red */
    const string GREEN = "\033[32m";              /* Green */
    const string YELLOW = "\033[33m";             /* Yellow */
    const string BLUE = "\033[34m";               /* Blue */
    const string MAGENTA = "\033[35m";            /* Magenta */
    const string CYAN = "\033[36m";               /* Cyan */
    const string WHITE = "\033[37m";              /* White */
    const string BOLDBLACK = "\033[1m\033[30m";   /* Bold Black */
    const string BOLDRED = "\033[1m\033[31m";     /* Bold Red */
    const string BOLDGREEN = "\033[1m\033[32m";   /* Bold Green */
    const string BOLDYELLOW = "\033[1m\033[33m";  /* Bold Yellow */
    const string BOLDBLUE = "\033[1m\033[34m";    /* Bold Blue */
    const string BOLDMAGENTA = "\033[1m\033[35m"; /* Bold Magenta */
    const string BOLDCYAN = "\033[1m\033[36m";    /* Bold Cyan */
    const string BOLDWHITE = "\033[1m\033[37m";   /* Bold White */
}

static std::string GetNow(
    const char *fmt = "%Y-%m-%d %H:%M:%S",
    int time_zone = 8)
{
    std::time_t unix_sec = std::time(nullptr);
    std::tm tm;
    static const int kHoursInDay = 24;
    static const int kMinutesInHour = 60;
    static const int kDaysFromUnixTime = 2472632;
    static const int kDaysFromYear = 153;
    static const int kMagicUnkonwnFirst = 146097;
    static const int kMagicUnkonwnSec = 1461;
    tm.tm_sec = unix_sec % kMinutesInHour;
    int i = (unix_sec / kMinutesInHour);
    tm.tm_min = i % kMinutesInHour; // nn
    i /= kMinutesInHour;
    tm.tm_hour = (i + time_zone) % kHoursInDay; // hh
    tm.tm_mday = (i + time_zone) / kHoursInDay;
    int a = tm.tm_mday + kDaysFromUnixTime;
    int b = (a * 4 + 3) / kMagicUnkonwnFirst;
    int c = (-b * kMagicUnkonwnFirst) / 4 + a;
    int d = ((c * 4 + 3) / kMagicUnkonwnSec);
    int e = -d * kMagicUnkonwnSec;
    e = e / 4 + c;
    int m = (5 * e + 2) / kDaysFromYear;
    tm.tm_mday = -(kDaysFromYear * m + 2) / 5 + e + 1;
    tm.tm_mon = (-m / 10) * 12 + m + 2;
    tm.tm_year = b * 100 + d - 6700 + (m / 10);
    stringstream ss;
    ss << std::put_time(&tm, fmt); // #include <iomanip>
    return ss.str();
}

static LogType logtype(LogType::Concole);
static std::ostream logstr(cout.rdbuf()); // 设置 buff 为 标准输出的 buff
static std::fstream file;
static std::string format;

void SetLogBuf(std::streambuf *buf)
{
    logstr.rdbuf(buf);
}

std::string_view nextFormat(std::string_view &src)
{
    auto end = src.end();

    if (src == "")
    {
        return "";
    }

    if (src.front() == '{')
    {
        auto pos_start = src.begin();

        if (pos_start == end)
            return "";

        pos_start += 1;

        auto pos_stop = find(pos_start, end, '}');

        if (pos_stop == end)
            return "";

        auto f = move(src.substr(pos_start - src.begin(), pos_stop - pos_start));
        src.remove_prefix(f.size() + 2);
        return f;
    }

    auto f = src.substr(0, 1);
    src.remove_prefix(1);
    return f;
}

/**
 * @brief
 *
 * @param level
 * @param format 格式化字符串 "{level} {time} {content} {file}:{line}"
 * @param log
 * @param file
 * @param line
 * @return * void
 */
void LogWriteFormat(const string level, const string format, const string log, const string file, const int line)
{
    std::string_view src{format};
    std::string_view f{""};

    while (!src.empty())
    {
        f = nextFormat(src);

        if (f.empty())
        {
            break;
        }
        else if ("time" == f)
        {
            if (LogType::Concole == logtype)
                logstr << ioscolor::YELLOW;
            logstr << GetNow();
        }
        else if ("level" == f)
        {
            if (LogType::Concole == logtype)
                logstr << ioscolor::GREEN;
            logstr << level;
        }
        else if ("content" == f)
        {
            if (LogType::Concole == logtype)
                logstr << ioscolor::RESET;
            logstr << log;
        }
        else if ("file" == f)
        {
            if (LogType::Concole == logtype)
                logstr << ioscolor::BLUE;
            logstr << file;
        }
        else if ("line" == f)
        {
            if (LogType::Concole == logtype)
                logstr << ioscolor::BLUE;
            logstr << line;
        }
        else
        {
            if (LogType::Concole == logtype)
            {
                logstr << ioscolor::RESET;
            }
            logstr << f;
        }
    }
    if (LogType::Concole == logtype)
        logstr << ioscolor::RESET;
    logstr << std::endl;
}

void LogWrite(const string level, const string log, const string file, const int line)
{
    if ("" != format)
    {
        LogWriteFormat(level, format, log, file, line);
        return;
    }

    if (logtype == LogType::Concole)
    {
        logstr
            << ioscolor::YELLOW
            << GetNow() << " "
            << ioscolor::GREEN
            << level << " "
            << ioscolor::RESET
            << log << " "
            << ioscolor::BLUE
            << file << ":" << line
            << ioscolor::RESET
            << std::endl;
    }
    else
    {
        logstr
            << GetNow() << " "
            << level << " "
            << log << " "
            << file << ":" << line
            << std::endl;
    }
}

void LogInit(LogType logtype_, std::string format_, std::string para)
{
    format = format_;
    logtype = logtype_;

    auto p = format.find(std::string("level"));
    if (p == std::string::npos)
    {
        format = "{level} " + format;
    }

    switch (logtype)
    {
    case LogType::File:
        if (file.is_open())
        {
            file.close();
        }

        file.open(para, ios::out | ios::binary | ios::app);
        if (!file.is_open())
        {
            throw std::runtime_error(std::string("无法打开文件:") + para);
        }

        SetLogBuf(file.rdbuf());
        break;

    case LogType::Concole:
    default:

        if (file.is_open())
        {
            file.close();
        }

        SetLogBuf(cout.rdbuf());
        break;
    }
}